/*
    Speedbump aka Project Kimbula
    Copyright (C) 2010  Ping Shin Ching

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses.

	Ping Shin Ching
	righteous.ninja@gmail.com OR ping_sc@hotmail.com
	More information at http://www.tidytutorials.com/p/speedbump.html
 */

#include "chunkedcontenthandler.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "logger.h"
#include "helpers.h"



/*************************************************************************
 * Display chunks for debug only 
 *************************************************************************/
void chunkedcontenthandler::showchunks(){
	Chunk *t;
	for(t = chunks; t; t=t->nextchunk){
		printchararray(t->data, t->datalength-2, "CHUNK Display");
	}
}


/*************************************************************************
 *  Free cunks
 *************************************************************************/
void chunkedcontenthandler::freechunks(){
	Chunk *oldchunk;
	while(chunks){
		oldchunk=chunks;
		chunks=chunks->nextchunk;
		if(oldchunk->data){
			free(oldchunk->data);
			oldchunk->data=0;
		}
		free(oldchunk);
	}
}


/*************************************************************************
 * Dechunk
 *************************************************************************/
FLATTEN_CHUNK_RESPONSE chunkedcontenthandler::flattenchunks(char** out, unsigned int* outsize, short x){
	Chunk *oldchunk;
	char* c, *tc;
	int l,  end;
	FLATTEN_CHUNK_RESPONSE resp;

	int tmpsl;

	l=0;
	tmpsl=0;
	c=0;
        
	end=(x?0:2);
	resp=FLATTEN_CHUNK_OK;
	while(chunks){
		char tmps[64]={0};

		//printf("CHUNKERS CHU 88 %d %d\n", chunks->datalength, chunks->actualdatalength);

		if(chunks->actualdatalength==-1){
			freechunks();
			resp=FLATTEN_CHUNK_PARTIAL;
			break;
		}
		if(x){
			sprintf(tmps, "%x\r\n",chunks->datalength-(chunks->datalength?2:0));
			tmpsl=strlen(tmps);
			if((tc=(char*)realloc(c, l+tmpsl))){
				c=tc;
				memcpy(c+l, tmps, tmpsl);
				l+=tmpsl;
			}else{
				if(c)
				free(c);
				freechunks();
				return FLATTEN_CHUNK_MEM_ERR ;
			}
		}
		if(chunks->datalength!=0){
			if((tc=(char*)realloc(c, l+chunks->actualdatalength-end))){
				c=tc;
				memcpy(c+l, chunks->data, chunks->actualdatalength-end);
         		l+=(chunks->actualdatalength-end);
			}
			else{
				if(c){
					free(c);
				}
				freechunks();
				return FLATTEN_CHUNK_MEM_ERR ;
			}
		}
		oldchunk=chunks;
		chunks=chunks->nextchunk;
		if(oldchunk->data)
			free(oldchunk->data);
			free(oldchunk);
	}	
	*out=c;
	*outsize=l;
	return resp;
}


/*************************************************************************
 * Constructor 
 *************************************************************************/
chunkedcontenthandler::chunkedcontenthandler(){
	content_length=0;
	buffer=0;
	buffersize=0;
	chunks=0;
	allchunkssize=0;
	capture=1;

	leftovers=0;
	leftoverslength=0;
	currentchunkdatasize=-1;

	approxchunksize=0;

	dechunking_completed=0;

	logd<<"Chunked: new chunk handler"<<lend;
}


/*************************************************************************
 * Destructor
 *************************************************************************/
chunkedcontenthandler::~chunkedcontenthandler(){
	if(buffer){
		free(buffer);
		buffer=0;
	}
	if(leftovers)
		free(leftovers);
		leftovers=0;
	freechunks();
}


/*************************************************************************
 * Called to handle data 
 *************************************************************************/
HANDLER_RESPONSE chunkedcontenthandler::HandleData(char** buf, unsigned int *bufsize){
	char* c, *b, *end;
	int s;
	short maxedout=0;

	//printchararray(*buf, *bufsize, "Pre CHUNK");

	if(leftovers && leftoverslength){
		logd<<"Chunked: first leftovers "<<leftoverslength<<lend;
		//printchararray(leftovers, leftoverslength, "LEFTOVERS Pre");
		leftovers=(char*)realloc(leftovers,leftoverslength+ *bufsize);
		memcpy(leftovers+leftoverslength, *buf, *bufsize);
		s=*bufsize+leftoverslength;
		b=leftovers;
		leftoverslength+=*bufsize;
		if(leftoverslength+approxchunksize > MAX_CHUNKCON_DATA_SIZE){
			logw<<"Chunked: Buffer breached "<<MAX_CHUNKCON_DATA_SIZE<<lend;
			//printf("leftovers buffer too big %d %d %d\n", approxchunksize, leftoverslength, *bufsize);
			return HANDLER_FINISHED;
		}
	}
	else{
		b=*buf;
		s=*bufsize;
	}
        
	while(s>0){
		logd<<"Chunked: "<<s<<" > 0"<<lend;
		char tmpchunk[64]={0};
		if(currentchunkdatasize==-1 || maxedout){
			if((c=strnstrn(b, s, "\r\n",2)) && !maxedout){
				logd<<"Chunked: got \\r\\n"<<lend;
				if(chunks==0){
					logd<<"Chunked: new chunk malloc"<<lend;
					if((chunks = (Chunk*)malloc(sizeof(Chunk))))
						currentChunk=chunks;
					else
						assert(1==2);
				}
				else{
					logd<<"Chunked: nextchunk chunk malloc"<<lend;
					if((currentChunk->nextchunk=(Chunk*)malloc(sizeof(Chunk)))){
						currentChunk=currentChunk->nextchunk;
					}
					else{
						assert(2==3);
					}
				}

				currentChunk->data=0;
				currentChunk->nextchunk=0;
				currentchunkdatasize=0;
				if(c-b >64-1){
					logd<<"Chunked: datalength with header - ignored for now"<<lend; 
					return HANDLER_ERROR;
				}
				memcpy(tmpchunk, b, c-b);

				currentChunk->actualdatalength=0;
				currentChunk->datalength=strtol(tmpchunk, &end, 16);
				if(currentChunk->datalength==0){
					logd<<"Chunked: finished chunking"<<lend; 
					dechunking_completed=1;
					return HANDLER_FINISHED; 
				}
				else
					currentChunk->datalength+=2; //collect the \r\n at the end too

				approxchunksize+=currentChunk->datalength;

				if( approxchunksize > MAX_CHUNKCON_DATA_SIZE){
					logw<<"Chunked: Buffer breached "<<MAX_CHUNKCON_DATA_SIZE<<lend;
					maxedout=1;
					continue;
				}	


				currentChunk->data=(char*)calloc(1,currentChunk->datalength);
				logd<<"Chunked: allocating data length "<<currentChunk->datalength<<lend;
				s=s-(c-b+2);
				b=c+2;
				//printf("h-decode %d %d\n", currentChunk->datalength, s);
				leftoverslength=0;
			}
			else{
				logd<<"Chunked: leftovers"<<lend;                        
				if(leftoverslength==0){
					char* old = leftovers;
					leftovers=(char*)malloc(s);
					memcpy(leftovers,b,s);
					leftoverslength=s;
					//printchararray(leftovers, leftoverslength, "LEFTOVERS");
					s=0;
					currentchunkdatasize=-1;
					if(old)
						free(old);
				}
				if(maxedout){
					currentChunk->actualdatalength=-1;
					//printchararray(leftovers, leftoverslength, " MAxedout and quitting");
					return HANDLER_FINISHED;
				}
				else
					return HANDLER_MORE;
			}
		}

		if(currentChunk->datalength-currentchunkdatasize<=s ){
			memcpy(currentChunk->data+currentchunkdatasize, b, currentChunk->datalength-currentchunkdatasize);
			if(currentChunk->datalength-currentchunkdatasize<s){
				logd<<"Chunked: copying to data with remainder"<<lend;
				b=b+(currentChunk->datalength-currentchunkdatasize);
				s-=(currentChunk->datalength-currentchunkdatasize);
			}
			else{
				logd<<"Chunked: copying to data in full\n"<<lend;
				s=0;
			}

			currentchunkdatasize=-1;
			//printchararray(currentChunk->data, currentChunk->datalength, "CHUNKed data");
			currentChunk->actualdatalength=currentChunk->datalength;
		}
		else{
			//if(currentChunk->datalength < currentchunkdatasize+s)
			//	printf("-------------%d %d %d\n", currentChunk->datalength, currentchunkdatasize, s);fflush(stdout);
			memcpy(currentChunk->data+currentchunkdatasize,b,s); 
			currentchunkdatasize+=s;
			currentChunk->actualdatalength=currentchunkdatasize;
			s=0;
		}
	}
	return HANDLER_MORE;
}


/*************************************************************************
 * Called  to set the header 
 *************************************************************************/
void chunkedcontenthandler::HandleHeaders(HeaderHandlerInterface* headerd){

	headerh = headerd;
	
	/*nothing required from headers*/
}


/*************************************************************************
 * Called (Should be only once) to get the handled data 
 *************************************************************************/
int chunkedcontenthandler::GetHandledData(char** buf, unsigned int* bufsize){
	char* cchunk;
	unsigned int flatchunksize;
	char tmps64[64];
	
	if(dechunking_completed){
		logd<<"Chunked: dechunking completed"<<lend;
		
		flattenchunks(&cchunk, &flatchunksize, 0);

		snprintf(tmps64, 64-1, "%d", flatchunksize);
		headerh->AddHeader("Content-Length", tmps64 );
		headerh->AddHeader("Spdbmp-chunked", "dechunked" );
		headerh->RemoveHeader("Transfer-Encoding" );
	}

	if(dechunking_completed){
		*buf=(char*)realloc(*buf, flatchunksize);
		memcpy(*buf,cchunk, flatchunksize);
		*bufsize=flatchunksize;
	}
	else{
		switch(flattenchunks(&cchunk, &flatchunksize, 1)){
			case FLATTEN_CHUNK_OK:
				logd<<"Chunked: chunk ok"<<lend;
				*buf=(char*)realloc(*buf,flatchunksize);
				memcpy(*buf,cchunk, flatchunksize);
				*bufsize=flatchunksize;
				break;
			case FLATTEN_CHUNK_PARTIAL:
				logd<<"Chunked: partial chunk"<<lend;
				*buf=(char*)realloc(*buf, flatchunksize+leftoverslength);
				memcpy(*buf,cchunk, flatchunksize);
				memcpy(*buf+flatchunksize ,leftovers, leftoverslength);
				*bufsize=flatchunksize+leftoverslength;
				break;
			case FLATTEN_CHUNK_MEM_ERR:
				break;
		}
	}

	if(cchunk)
		free(cchunk);
	if(leftovers){
		free(leftovers);
		leftovers=0;
	}

	//printchararray(*buf, *bufsize, "EVERYTHING");
	if(dechunking_completed)
		return HANDLER_FINISHED;
	else
		return HANDLER_COPOUT;
}


